package org.ns.summer.data.mybatis;

import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.ns.summer.aop.AopUtils;
import org.ns.summer.aop.ProxyFactoryBean;
import org.ns.summer.beans.ApplicationContext;
import org.ns.summer.beans.BeanPostProcessor;
import org.ns.summer.beans.anns.Component;
import org.ns.summer.beans.beanprocessor.ApplicationContextAware;
import org.ns.summer.beans.error.BeansException;
import org.ns.summer.core.AnnotationUtils;
import org.ns.summer.core.FieldUtils;
import org.ns.summer.core.MethodUtils;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


@Component
public class MybatisBeanProcessor implements BeanPostProcessor, ApplicationContextAware {

    private ApplicationContext applicationContext;
    private Map<String , SqlSessionFactory> sessionFactoryMap = new ConcurrentHashMap<>();
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Class<?> targetType = AopUtils.getTargetType(bean.getClass());
        // 注入数据源
        dbsourceInjector(targetType, bean, beanName);

        return createTransactionProxy(targetType, bean, beanName);
    }
    private void dbsourceInjector(Class<?> targetType, Object bean, String beanName) {
        List<Field> fieldsAll = FieldUtils.findFieldsAll(targetType, DBSource.class);
        for (Field field : fieldsAll) {
            dbsourceInjector(targetType, bean, beanName, field);
        }

    }
    private void dbsourceInjector(Class<?> targetType, Object bean, String beanName, Field field) {
        Class<?> mapperClass = field.getType();

        DBSource dbSource = (DBSource)AnnotationUtils.getAnnotation(field, DBSource.class);
        String dataSourceName = dbSource.value().trim();
        if (dataSourceName.isEmpty()) {
            throw new DBSourceInjectorException("数据源名称不能是空！！！");
        }
        SqlSessionFactory sqlSessionFactory = sessionFactoryMap.get(dataSourceName);
        if (sqlSessionFactory == null) {
            Object object = applicationContext.getBean(dataSourceName);
            if (!(object instanceof DataSource) && !(object instanceof SqlSessionFactory)) {
                throw new DBSourceInjectorException("获得的数据源, 不是有效的类型！！！");
            }
            if (object instanceof SqlSessionFactory) {
                sqlSessionFactory = (SqlSessionFactory) object;
            } else if (object instanceof DataSource) {
                DataSource dataSource = (DataSource) object;
                Environment environment
                         = new Environment(dataSourceName, new JdbcTransactionFactory(), dataSource);
                Configuration configuration = new Configuration(environment);
                // configuration.addInterceptor(); // 获得插件添加插件，这里暂不实现
                // configuration.addCache();
                // configuration.setLazyLoadingEnabled(true);
                // configuration.setAggressiveLazyLoading(true);
                SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
                sqlSessionFactory = factoryBuilder.build(configuration);

            }
            this.sessionFactoryMap.put(dataSourceName, sqlSessionFactory);
        }
        try {

            if (mapperClass == SqlSessionFactory.class) {
                field.set(bean, sqlSessionFactory);
            } else if (mapperClass == DataSource.class) {
                Object mapper = Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, new DataSourceInvokeProxy(sqlSessionFactory));
                field.set(bean, mapper);
            } else if (mapperClass.isInterface()) {
                Configuration configuration = sqlSessionFactory.getConfiguration();
                if (!configuration.hasMapper(mapperClass)) {
                    configuration.addMapper(mapperClass);
                }
                MybatisMapperInvokeProxy mapperInvokeProxy = new MybatisMapperInvokeProxy(sqlSessionFactory, mapperClass);
                Object mapper = Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, mapperInvokeProxy);
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }

                field.set(bean, mapper);

            }
        } catch (Exception e) {
            throw new DBSourceInjectorException("mapper:" + field.getName(), e);
        }

    }


    private Object createTransactionProxy(Class<?> targetType, Object bean, String beanName) {
        if (MethodUtils.existMethodByAnnotation(targetType, Transaction.class)) {

            List<Method> methods = MethodUtils.findMethodByAnnotation(targetType, Transaction.class);
            Map<Method, Transaction> transactionMap = new HashMap<>();
            for (Method method : methods) {
                Transaction transaction = (Transaction)AnnotationUtils.getAnnotation(method, Transaction.class);
                transactionMap.put(method, transaction);
            }
            ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
            proxyFactoryBean.setTarget(bean);
            proxyFactoryBean.setInterceptor(new MybatisTransactionProxy(transactionMap));
            bean = proxyFactoryBean.getObject();
        }
        return bean;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
}
